package com.smzd.ccms.core;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;

import org.apache.commons.lang.StringUtils;

import com.smzd.ccms.domain.Complet;
import com.smzd.ccms.domain.CompletConfig;

import freemarker.core.Environment;
import freemarker.core.Environment.Namespace;
import freemarker.ext.servlet.HttpRequestHashModel;
import freemarker.template.Template;
import freemarker.template.TemplateDirectiveBody;
import freemarker.template.TemplateDirectiveModel;
import freemarker.template.TemplateException;
import freemarker.template.TemplateModel;
import freemarker.template.TemplateModelException;

/**
 * 小组件的标签处理器。<br>
 * 传统的MVC模式被拆散称为页面当中的微型MVC，每一个微型MVC单元称为一个小组件。<br>
 * 小组件有自己的标签配置参数，也有自己的内容逻辑处理单元，并有自己的视图系统。<br>
 * 但小组件依附于传统MVC模式存在，这个依附在CCMS的核心约定中得到实现。<br>
 * 一个请求会有自己的参数和视图，参数将被统一存放在ModelMap中，而视图在调用过程中将触发每个小组件的处理标签的调用，
 * 即进入到本类的execute方法中。该类将处理当前小标签的内容处理，并调用合适的视图将其渲染起来，并在请求的输出中展示内容。
 * 
 * @author YuZhongda
 * 
 */
public class CompletTag implements TemplateDirectiveModel {
	@SuppressWarnings({ "rawtypes", "unchecked" })
	@Override
	public void execute(Environment env, Map params, TemplateModel[] loopVars, TemplateDirectiveBody body)
	        throws TemplateException, IOException {
		String spotid = null;
		// 获取小组件的编号
		if (params != null && !params.isEmpty()) {
			Object p = params.get(CCMSConstant.KEY_SPOTID);
			if (p != null) {
				spotid = p.toString();
			}
		}
		try {
			// 1. 获取请求的Request。这个一定有。
			HttpServletRequest request = getHttpServletRequest(env);
			// 2. =>来自Controller的model数据。这个可能没有。如果没有怎么办？
			Map<String, Object> model = (Map<String, Object>) request.getAttribute(CCMSConstant.KEY_MODEL_MAP);
			if (model == null) {
				model = new HashMap<String, Object>();
			}
			// =>这个也不一定有哦，model可能是新new出来的。
			ViewMapper mapper = (ViewMapper) model.get(CCMSConstant.KEY_VIEWMAPPER_IN_MODEL_MAP);// //
			// 3. =>
			EntityWrapper entityWrapper = null;
			if (mapper != null) {
				entityWrapper = mapper.getEntityWrapper();
			}
			// 4. CompletConfig不一定存在，因而Complet的获取也就有不同的方式，下面分别来获取这两个对象
			CompletConfig completConfig = null;
			Complet complet = null;
			// 小组件编号存在，则继续，否则后续流程无法进行
			if (spotid != null && entityWrapper != null) {
				completConfig = entityWrapper.getChannelConfig().getCompletConfigBySpotid(Integer.parseInt(spotid));
				if (completConfig != null) {
					complet = completConfig.getComplet();
				}
			}
			if (complet == null) {
				// 模板内直接配置的小组件，使用直接指定组件的className的方式配置组件，并且直接配置组件所需要的参数。
				// 获取组件的className
				String className = getCompletClassName(params);
				// 根据className获取组件
				complet = CompletPluginManager.getComplet(className);
			}
			// 获得组件执行器
			CompletPlugin executer = complet.getExecuter();
			// 参数包装
			ParamsWrapper pw = new ParamsWrapper(params, model, entityWrapper, completConfig, request);
			// 执行组件
			executeComplet(env, body, executer, pw);

		} catch (Exception e) {
			renderBodyAsView(env, body, "<!--" + e.getMessage() + "-->");
		}
	}

	@SuppressWarnings("rawtypes")
	private String getCompletClassName(Map params) {
		String className = null;
		if (params != null && !params.isEmpty()) {
			Object p = params.get(CCMSConstant.KEY_COMPLET_CLASS);
			if (p != null) {
				className = p.toString();
			}
		}
		return className;
	}

	private void executeComplet(Environment env, TemplateDirectiveBody body, CompletPlugin executer, ParamsWrapper pw)
	        throws TemplateModelException {
		Namespace ns = env.getCurrentNamespace();
		try {
			// 调用小组件的处理程序获得小组件的视图所需要的数据，并拿到小组件的view
			String view = executer.execute(pw, ns);
			// 备份数据，避免全局数据被小组件的渲染污染
			Map<String, TemplateModel> originMap = backupData(pw, ns);
			// 把小组件的数据注入进去
			ns.putAll(pw.getOutParams());
			// 渲染组件
			try {
				renderCompletView(env, body, view);
			} finally {
				// 恢复数据，拿出来的数据再倾倒回去。
				ns.putAll(originMap);
			}
		} catch (Exception e) {
			System.out.println("Complet execute error!!!");
			e.printStackTrace();
		}
	}

	private void renderCompletView(Environment env, TemplateDirectiveBody body, String view) throws IOException,
	        TemplateException {
		if (!StringUtils.isEmpty(view) && view.endsWith("ftl")) {
			view = "ccms/complets/" + view;
			// 渲染小模块
			Template template = env.getTemplateForInclusion(view, null, true);
			if (template != null) {
				env.include(template);
			} else {
				renderBodyAsView(env, body, "<!-- 小组件的模板文件没找到！view=" + view + "-->");
			}
		} else if (body != null) {// 没有返回视图的话，认为小组件的body就是其模板，称为直接模板，plugin执行的结果只要往第一个参数map中放结果数据就可以
			body.render(env.getOut());
		}
	}

	private Map<String, TemplateModel> backupData(ParamsWrapper pw, Namespace ns) throws TemplateModelException {
		// 备份全局中的数据，准备注入小组件所要的数据
		Map<String, TemplateModel> originMap = new HashMap<String, TemplateModel>();
		// 先把小组件输出的参数备份出来
		for (String key : pw.keySet()) {
			TemplateModel value = ns.get(key);
			if (value != null) {
				originMap.put(key, value);
			}
		}
		// 下面几个有用没用啊，会冲突吗？
		// 备份数据Config
		TemplateModel value = ns.get(CCMSConstant.KEY_CONFIG_PARAMS);
		if (value != null) {
			originMap.put(CCMSConstant.KEY_CONFIG_PARAMS, value);
		}
		// 备份数据TAG
		value = ns.get(CCMSConstant.KEY_TAG_PARAMS);
		if (value != null) {
			originMap.put(CCMSConstant.KEY_TAG_PARAMS, value);
		}
		// 备份数据Model
		value = ns.get(CCMSConstant.KEY_MODEL_PARAMS);
		if (value != null) {
			originMap.put(CCMSConstant.KEY_MODEL_PARAMS, value);
		}
		return originMap;
	}

	private void renderBodyAsView(Environment env, TemplateDirectiveBody body, String message) throws IOException,
	        TemplateException {
		env.getOut().write(message);
		if (body != null) {
			body.render(env.getOut());
		}
	}

	protected HttpServletRequest getHttpServletRequest(Environment env) throws TemplateModelException {
		HttpRequestHashModel httpRequestHM = (HttpRequestHashModel) env.getGlobalVariable(CCMSConstant.KEY_HTTPREQUEST);
		HttpServletRequest request = httpRequestHM.getRequest();
		return request;
	}
}
